/* (The MIT License)
Copyright (c) 2006 Adam Bennett (cruxic@gmail.com)
 
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
 */
package adamb;

import java.io.*;
import java.nio.charset.*;
import java.util.Properties;
import adamb.vorbis.*;
import adamb.ui.*;
import adamb.util.Util;

import java.io.IOException;
import java.awt.event.*;
import javax.swing.*;

/**
 *
 * @author  cruxic
 */
public class JVorbisCommentGUI extends javax.swing.JFrame
{
	private static final String PREFS_FILE_NAME = ".JVorbisCommentGUI.properties";
	private File fileBeingEdited;
	private String initialFile;
	
	/** Creates new form JVorbisCommentGUI */
	public JVorbisCommentGUI(String initialFile)
	{
		this.initialFile = initialFile;
		initComponents();
		
		setDirty(false);
		
		lstComments.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		//start with empty comments
		lstComments.setModel(new CommentListModel(new VorbisCommentHeader()));
		
		bindKey(lstComments, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), new EnterAction());
		bindKey(lstComments, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), new DeleteAction());
		bindKey(txtFile, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), new EnterFileAction());
	}
	
	private void bindKey(JComponent component, KeyStroke keyStroke, Action action)
	{
		component.getInputMap().put(keyStroke, action);
		component.getActionMap().put(action, action);
	}
	
	private void setDirty(boolean dirty)
	{
		btnSave.setEnabled(dirty);
	}
	
	private boolean isDirty()
	{
		return btnSave.isEnabled();
	}
	
	/** This method is called from within the constructor to
	 * initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is
	 * always regenerated by the Form Editor.
	 */
  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
  private void initComponents()
  {
    jScrollPane1 = new javax.swing.JScrollPane();
    lstComments = new javax.swing.JList();
    jLabel1 = new javax.swing.JLabel();
    txtFile = new javax.swing.JTextField();
    btnBrowse = new javax.swing.JButton();
    jLabel2 = new javax.swing.JLabel();
    btnEdit = new javax.swing.JButton();
    btnAdd = new javax.swing.JButton();
    btnDel = new javax.swing.JButton();
    btnSave = new javax.swing.JButton();
    btnCancel = new javax.swing.JButton();
    btnAbout = new javax.swing.JButton();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    setTitle("Ogg Vorbis Comment Editor - Cruxic");
    addWindowListener(new java.awt.event.WindowAdapter()
    {
      public void windowOpened(java.awt.event.WindowEvent evt)
      {
        formWindowOpened(evt);
      }
    });

    lstComments.addMouseListener(new java.awt.event.MouseAdapter()
    {
      public void mouseClicked(java.awt.event.MouseEvent evt)
      {
        lstCommentsMouseClicked(evt);
      }
    });

    jScrollPane1.setViewportView(lstComments);

    jLabel1.setDisplayedMnemonic('f');
    jLabel1.setLabelFor(txtFile);
    jLabel1.setText("Ogg Vorbis file:");

    txtFile.setText("<Click browse to select a file>");

    btnBrowse.setMnemonic('B');
    btnBrowse.setText("Browse...");
    btnBrowse.addActionListener(new java.awt.event.ActionListener()
    {
      public void actionPerformed(java.awt.event.ActionEvent evt)
      {
        btnBrowseActionPerformed(evt);
      }
    });

    jLabel2.setDisplayedMnemonic('m');
    jLabel2.setLabelFor(lstComments);
    jLabel2.setText("Comments");

    btnEdit.setMnemonic('E');
    btnEdit.setText("Edit");
    btnEdit.addActionListener(new java.awt.event.ActionListener()
    {
      public void actionPerformed(java.awt.event.ActionEvent evt)
      {
        btnEditActionPerformed(evt);
      }
    });

    btnAdd.setMnemonic('A');
    btnAdd.setText("Add");
    btnAdd.addActionListener(new java.awt.event.ActionListener()
    {
      public void actionPerformed(java.awt.event.ActionEvent evt)
      {
        btnAddActionPerformed(evt);
      }
    });

    btnDel.setMnemonic('R');
    btnDel.setText("Remove");
    btnDel.addActionListener(new java.awt.event.ActionListener()
    {
      public void actionPerformed(java.awt.event.ActionEvent evt)
      {
        btnDelActionPerformed(evt);
      }
    });

    btnSave.setMnemonic('p');
    btnSave.setText("Apply Changes to File");
    btnSave.addActionListener(new java.awt.event.ActionListener()
    {
      public void actionPerformed(java.awt.event.ActionEvent evt)
      {
        btnSaveActionPerformed(evt);
      }
    });

    btnCancel.setMnemonic('C');
    btnCancel.setText("Close");
    btnCancel.addActionListener(new java.awt.event.ActionListener()
    {
      public void actionPerformed(java.awt.event.ActionEvent evt)
      {
        btnCancelActionPerformed(evt);
      }
    });

    btnAbout.setText("?");
    btnAbout.addActionListener(new java.awt.event.ActionListener()
    {
      public void actionPerformed(java.awt.event.ActionEvent evt)
      {
        btnAboutActionPerformed(evt);
      }
    });

    org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
      .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
          .add(jLabel1)
          .add(jLabel2)
          .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
            .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
              .add(layout.createSequentialGroup()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                  .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 286, Short.MAX_VALUE)
                  .add(txtFile, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 286, Short.MAX_VALUE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED))
              .add(layout.createSequentialGroup()
                .add(btnSave)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(btnCancel)
                .add(47, 47, 47)))
            .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
              .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false)
                .add(org.jdesktop.layout.GroupLayout.TRAILING, btnBrowse, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .add(org.jdesktop.layout.GroupLayout.TRAILING, btnEdit, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .add(org.jdesktop.layout.GroupLayout.TRAILING, btnAdd, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .add(org.jdesktop.layout.GroupLayout.TRAILING, btnDel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
              .add(btnAbout))))
        .addContainerGap())
    );
    layout.setVerticalGroup(
      layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
      .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(jLabel1)
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
          .add(btnBrowse)
          .add(txtFile, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(jLabel2)
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
          .add(layout.createSequentialGroup()
            .add(btnEdit)
            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
            .add(btnAdd)
            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
            .add(btnDel))
          .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 197, Short.MAX_VALUE))
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
          .add(btnSave)
          .add(btnCancel)
          .add(btnAbout))
        .addContainerGap())
    );
    pack();
  }// </editor-fold>//GEN-END:initComponents

	private void btnAboutActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnAboutActionPerformed
	{//GEN-HEADEREND:event_btnAboutActionPerformed
		InputStream is = getClass().getClassLoader().getResourceAsStream("about.txt");
		try
		{
			String txt = Util.readTextStream(is, Charset.forName("UTF-8"));
			is.close();
			
			JTextArea text = new JTextArea(txt, 20, 40);
			text.setWrapStyleWord(true);
			text.setLineWrap(true);
			text.setEditable(false);
			
			JOptionPane.showMessageDialog(this, new JScrollPane(text), "About JVorbisCommentGUI", JOptionPane.INFORMATION_MESSAGE);
		}
		catch (IOException ieo)
		{
			ieo.printStackTrace();
		}
	}//GEN-LAST:event_btnAboutActionPerformed
	

	/**called when the window is first opened*/
	private void formWindowOpened(java.awt.event.WindowEvent evt)//GEN-FIRST:event_formWindowOpened
	{//GEN-HEADEREND:event_formWindowOpened
		//if the application was started with an initial file parameter then try load the file
		if (initialFile != null)
		{
			loadFile(new File(initialFile));
			initialFile = null;  //let the GC have it
		}
	}//GEN-LAST:event_formWindowOpened
	
	private void btnSaveActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnSaveActionPerformed
	{//GEN-HEADEREND:event_btnSaveActionPerformed
		CommentListModel clm = (CommentListModel)lstComments.getModel();
		try
		{
			VorbisIO.writeComments(fileBeingEdited, clm.comments);
			setDirty(false);
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
			String msg = getExceptionStack(ioe);
			JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE);
		}
		
	}//GEN-LAST:event_btnSaveActionPerformed
	
	private String getExceptionStack(Throwable t)
	{
		StringWriter sw = new StringWriter();
		t.printStackTrace(new PrintWriter(sw));
		return sw.getBuffer().toString();
	}
	
	private void btnDelActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnDelActionPerformed
	{//GEN-HEADEREND:event_btnDelActionPerformed
		//have a selection?
		int idx = lstComments.getSelectedIndex();
		if (idx != -1)
		{
			CommentListModel clm = (CommentListModel)lstComments.getModel();
			clm.remove(idx);
			setDirty(true);
		}
	}//GEN-LAST:event_btnDelActionPerformed
	
	private void btnAddActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnAddActionPerformed
	{//GEN-HEADEREND:event_btnAddActionPerformed
		//do we have a loaded file to add to?
		if (fileBeingEdited != null)
		{
			CommentField cf = new CommentField("", "");
			FrmEditField frm = new FrmEditField(this, cf);
			frm.setVisible(true);
			if (frm.changed)
			{
				CommentListModel clm = (CommentListModel)lstComments.getModel();
				clm.add(cf);
				setDirty(true);
			}
		}
	}//GEN-LAST:event_btnAddActionPerformed
	
	private void lstCommentsMouseClicked(java.awt.event.MouseEvent evt)//GEN-FIRST:event_lstCommentsMouseClicked
	{//GEN-HEADEREND:event_lstCommentsMouseClicked
		//double click on an item to edit it.
		if (evt.getButton() == MouseEvent.BUTTON1
			&& evt.getClickCount() == 2)
			btnEditActionPerformed(null);
		
	}//GEN-LAST:event_lstCommentsMouseClicked
	
	private void btnEditActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnEditActionPerformed
	{//GEN-HEADEREND:event_btnEditActionPerformed
		int idx = lstComments.getSelectedIndex();
		if (idx != -1)
		{
			CommentListModel clm = (CommentListModel)lstComments.getModel();
			CommentField cf = clm.comments.fields.get(idx);
			FrmEditField frm = new FrmEditField(this, cf);
			frm.setVisible(true);
			if (frm.changed)
			{
				clm.fireChanged(idx);
				setDirty(true);
			}
		}
	}//GEN-LAST:event_btnEditActionPerformed
	
	private void btnCancelActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnCancelActionPerformed
	{//GEN-HEADEREND:event_btnCancelActionPerformed
		System.exit(0);
	}//GEN-LAST:event_btnCancelActionPerformed
	
	private File getUserHome()
	{
		String s = System.getProperty("user.home");
		if (s != null)
			return new File(s);
		else
			return new File(".");
	}
	
	private void savePreferences(File lastDir)
	throws IOException
	{
		Properties props = new Properties();
		props.setProperty("lastDir", lastDir.getPath());
		
		FileOutputStream fos = new FileOutputStream(new File(getUserHome(), PREFS_FILE_NAME), false);
		props.store(fos, "User preferences for JVorbisCommentGUI");
		fos.close();
	}
	
	private File getLastDir()
	{
		Properties props = new Properties();
		
		try
		{
			FileInputStream fis = new FileInputStream(new File(getUserHome(), PREFS_FILE_NAME));
			props.load(fis);
			fis.close();
			
			String s = props.getProperty("lastDir");
			if (s != null)
				return new File(s);
		}
		catch (IOException ioe)
		{
			//if we fail to load the preference we shouldn't bother the user about it because it's rather trivial
			System.err.println("Error loading preferences: " + ioe);
		}
		
		return null;
	}
	
	private void loadFile(File file)
	{
		/*the user has attempted to open a file.  Remember it so that
		 the next time they browse for a file we can remember which
		 directory they were last in*/
		try
		{
			//File parent = file.getCanonicalFile().getParentFile();
			//if (parent != null && parent.isDirectory())
			savePreferences(file.getCanonicalFile());
		}
		catch (IOException ioe)
		{
			//if we fail to save the preferences we shouldn't bother the user about it because it's rather trivial
			System.err.println(ioe);
		}
		
		boolean load = true;
		if (isDirty())
		{
			Object[] options = new Object[]{"Save", "Don't save", "Cancel"};
			int choice =  JOptionPane.showOptionDialog(this,
				"Do you want to save your changes to\n\"" + fileBeingEdited.getName()
				+ "\"\nbefore loading a new file?",
				"Save changes?", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
				null, options, options[0]);
			
			//save first?
			if (choice == JOptionPane.YES_OPTION)
				btnSaveActionPerformed(null);
			else if (choice == JOptionPane.CANCEL_OPTION)
				load = false;
		}
		
		if (load)
		{
			try
			{
				VorbisCommentHeader vch = VorbisIO.readComments(file);
				fileBeingEdited = file;
				lstComments.setModel(new CommentListModel(vch));
				setDirty(false);
				txtFile.setText(file.getPath());
			}
			catch (FileNotFoundException fnf)
			{
				//FileNotFoundException will be the most common error so just show a polite error
				JOptionPane.showMessageDialog(this, fnf.getMessage(), "File Not Found", JOptionPane.ERROR_MESSAGE);
			}
			catch (IOException ioe)
			{
				ioe.printStackTrace();
				String msg = getExceptionStack(ioe);
				JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE);
			}
		}
	}
	
	private void btnBrowseActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnBrowseActionPerformed
	{//GEN-HEADEREND:event_btnBrowseActionPerformed
		File file = SwingUtil.chooseExistingFile(this, "Choose an Ogg Vorbis file", new String[]{".ogg"}, getLastDir());
		if (file != null)
			loadFile(file);
	}//GEN-LAST:event_btnBrowseActionPerformed
	
	/**
	 * @param args the command line arguments
	 */
	public static void main(String args[])
	{
		Starter starter = new Starter(args.length > 0 ? args[0]: null);
		java.awt.EventQueue.invokeLater(starter);
		starter = null;  //don't this our reference to this anymore
	}
	
	
  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.JButton btnAbout;
  private javax.swing.JButton btnAdd;
  private javax.swing.JButton btnBrowse;
  private javax.swing.JButton btnCancel;
  private javax.swing.JButton btnDel;
  private javax.swing.JButton btnEdit;
  private javax.swing.JButton btnSave;
  private javax.swing.JLabel jLabel1;
  private javax.swing.JLabel jLabel2;
  private javax.swing.JScrollPane jScrollPane1;
  private javax.swing.JList lstComments;
  private javax.swing.JTextField txtFile;
  // End of variables declaration//GEN-END:variables
	
	private class EnterAction extends AbstractAction
	{
		public void actionPerformed(ActionEvent evt)
		{
			btnEditActionPerformed(null);
		}
	}
	
	private class EnterFileAction extends AbstractAction
	{
		public void actionPerformed(ActionEvent evt)
		{
			loadFile(new File(txtFile.getText()));
		}
	}
	
	private class DeleteAction extends AbstractAction
	{
		public void actionPerformed(ActionEvent evt)
		{
			btnDelActionPerformed(null);
		}
	}
}

class Starter implements Runnable
{
	private String initialFile;
	public Starter(String initialFile)
	{
		this.initialFile = initialFile;
	}
	
	public void run()
	{
		new JVorbisCommentGUI(initialFile).setVisible(true);
	}
}
